﻿#define PROPAGATE_NAMES

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows.Markup;

using agree.schema;

using alib;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;

namespace agree.composition
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	/// regarding XAML markup extensions which provide a container for late-binding loaders, aka 'Factories'
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public sealed class Propagations : IdentList<Propagation>
	{
		public Propagations(IIdentity prv)
			: base(prv)
		{
		}
		public Propagations()
			: this(default(IIdentity))
		{
		}
	};

	public abstract class Propagation : identity_base
	{
		public Propagation(IIdentity prv)
			: base(prv)
		{
		}
		public Propagation()
			: this(default(IIdentity))
		{
		}
		public Object Property { get; set; }
	};

	/// <summary>
	/// 20130401 nested markup extensions in the Value are fully resolved at bind-time. If this proves unsatisfactory,
	/// then secondary resolutions can be moved to ApplySetters in xaml_functor_propagations
	/// </summary>
	[ContentProperty("Value")]
	public sealed class Setter : Propagation
	{
		public Setter(IIdentity prv)
			: base(prv)
		{
		}
		public Setter()
			: this(default(IIdentity))
		{
		}
		Object value;
		public Object Value
		{
			get { return this.value; }
			set
			{
				MarkupExtension me;
				while ((me = value as MarkupExtension) != null && value != (value = me.ProvideValue(null)))
					;
				this.value = value;
			}
		}
	};

	public sealed class Publisher : Propagation
	{
		public Publisher(IIdentity prv)
			: base(prv)
		{
		}
		public Publisher()
			: this(default(IIdentity))
		{
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[UsableDuringInitialization(true)]
	public abstract class xaml_functor_monad : _monad, IFunctor
	{
		protected xaml_functor_monad(IRtParent parent)
			: base(parent, parent)
		{
		}

		IFunctor m;
		public IFunctor Monad { get { return m; } }
		public Delegate Delegate { get { return m.Delegate; } }
		public Type[] Targs { get { return m.Targs; } }
		public IFunctor Bind(IFunctor g) { return m.Bind(g); }

		protected abstract IFunctor compile();

		/// <summary> This is called *after* EndInit </summary>
		public override Object ProvideValue(IServiceProvider sp)
		{
			base.ProvideValue(sp);		// provides error checking only

			if (this.m != null)
				throw new Exception();	// should be called only once
			if (sp is IFunctor)
				throw new Exception();	// monad creating another monad?

			var m_sub = compile();

#if PROPAGATE_NAMES
			var _name = this.Name;
			if (_name != null && PP.Name.GetValue(m_sub) == null)
				PP.Name.SetValue(m_sub, _name);
#endif

			return this.m = m_sub;
		}

		namescope_helper _ns;
		namescope_helper ns()
		{
			namescope_helper _new;
			return _ns ?? Interlocked.CompareExchange(ref _ns, _new = new namescope_helper(), null) ?? _new;
		}

		public new INameScope NameScope { get { return ns(); } }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Propagations")]
	public abstract class xaml_functor_propagations : xaml_functor_monad
	{
		public xaml_functor_propagations(IRtParent parent)
			: base(parent)
		{
		}

		Propagations _prps;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public Propagations Propagations
		{
			get
			{
				if (_prps == null)
					_prps = new Propagations(this);
				return _prps;
			}
			set
			{
				if (_prps == null)
					_prps = value;
				else
					_prps.AddRange(value);
			}
		}

		public void ApplySetters(IIdentity item)
		{
			if (_prps == null)
				return;

			var t_item = item.GetType();

			foreach (var _prp in _prps)
			{
				Setter setter;
				if ((setter = _prp as Setter) == null)
					continue;

				MethodInfo mi;
				if ((mi = find_setter(setter.Property, t_item)) == null)
					continue;

				var value = setter.Value;

				Type pt;
				if (!(pt = mi.GetParameterType(0)).IsAssignableFrom(value.GetType()))
					if ((value = Convert.ChangeType(value, pt)) == null)
						throw new Exception();

				mi.Invoke(item, new[] { value });
			}
		}

		MethodInfo find_setter(Object wh, Type tt)
		{
			if (wh == null)
				return null;

			MethodInfo mi;
			String s;
			schema_prop p;
			if ((p = wh as schema_prop) != null)
			{
				mi = p.SetMethod;
			}
			else if ((s = wh as String) != null)
			{
				s = s.Trim();
				if ((mi = tt.GetMethod("set_" + s, (BindingFlags)0x15)) == null)
					throw new Exception(String.Format("Property {0} not found on object of type {1}", s, tt._Name()));
			}
			else /* PathRef-style walk? */
			{
				throw new Exception();
			}
			return mi;
		}

#if false
		public void DoPublishing(IIdentity item)
		{
			if (_prps == null)
				return;

			var t_item = item.GetType();

			foreach (var _prp in _prps)
			{
				Publisher publishr;
				if ((publishr = _prp as Publisher) == null)
					continue;

				//s.SetItemValue(item, s.Value);
			}
		}
#endif
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class Factory<G> : xaml_functor_propagations
		where G : IMonad
	{
		public Factory(IRtParent parent)
			: base(parent)
		{
		}

		protected override IFunctor compile()
		{
			Type[] Targs;

			if (typeof(IFunctor).IsAssignableFrom(typeof(G)))
			{
				Debug.Print("warning: {0} is already IFunctor; composing with singleton instance instead of Factory wrapper", typeof(G)._Name());

				var f = typeof(G)._ctor<IFunctor>(this);

				ApplySetters(f);

				return f;
			}
			else
			{
				Targs = typeof(G).FindGenericInterface(typeof(IMonad<,>)).GetGenericArguments();

				return typeof(f_factory<,,>).MakeGenericType(typeof(G), Targs[0], Targs[1])._ctor<IFunctor>(this);
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public abstract class xaml_functor_container : xaml_functor_monad, IAddArray<IIdentity>
	{
		public xaml_functor_container(IRtParent parent)
			: base(parent)
		{
		}

		IIdentity[] items;
		public IIdentity[] ItemsArr
		{
			get { return items ?? alib.Collections.Collection<IIdentity>.None; }
		}

		public IIdentity this[int index] { get { return items[index]; } }

		public int Count { get { var _tmp = items; return _tmp == null ? 0 : _tmp.Length; } }

		public void Add(IIdentity item)
		{
			alib.Array.arr.AppendSafe(ref items, item);
		}

		public IEnumerator<IIdentity> GetEnumerator()
		{
			var _tmp = items;
			for (int i = 0; i < _tmp.Length; i++)
				yield return _tmp[i];
		}

		IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

		void ICollection.CopyTo(Array array, int index)
		{
			var _tmp = items;
			for (int i = 0; i < _tmp.Length; i++)
				array.SetValue(_tmp[i], index++);
		}

		bool ICollection.IsSynchronized { get { throw not.impl; } }

		Object ICollection.SyncRoot { get { throw not.impl; } }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Functor")]
	public abstract class xaml_functor_unary : xaml_functor_monad
	{
		public xaml_functor_unary(IRtParent parent)
			: base(parent)
		{
			this.functor = f_identity.Instance;
		}

		protected IFunctor functor;
		public IFunctor Functor
		{
			get { return this.functor; }
			set { this.functor = value; }
		}

		protected override IFunctor compile()
		{
			return this.functor;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class Composition : xaml_functor_unary
	{
		public Composition(IRtParent parent)
			: base(parent)
		{
		}

#if false
		protected override IFunctor compile()
		{
			var defer = _farg;
			if (f_base.IsDeferredItem(defer))
			{
				Debug.Print("{0} doesn't need deferral", defer.GetType()._Name());

				return defer;
			}

			Debug.Print("applying deferral to {0}", defer.GetType()._Name());

			var Tc = typeof(f_defer<,>).MakeGenericType(defer.Targs[0], defer.Targs[1]);

			return Tc._ctor<IFunctor>(this, defer);
		}
#endif
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class First : xaml_functor_unary
	{
		public First(IRtParent parent)
			: base(parent)
		{
		}

		protected override IFunctor compile()
		{
			var Titem = f_compose.GetItemsArgType(functor.Targs[1]);

			return typeof(f_first<,>).MakeGenericType(functor.Targs[0], Titem)._ctor<IFunctor>(this, functor);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class Sequence : xaml_functor_container
	{
		public Sequence(IRtParent parent)
			: base(parent)
		{
		}

		protected override IFunctor compile()
		{
			var _tmp = ItemsArr;
			return _tmp == null || _tmp.Length == 0 ? f_identity.Instance : f_compose.Instance.Func(_tmp);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class Map : xaml_functor_container
	{
		public Map(IRtParent parent)
			: base(parent)
		{
		}

		protected override IFunctor compile()
		{
			var m = f_compose.Instance.Func(ItemsArr);

			return typeof(f_project<,>).MakeGenericType(m.Targs)._ctor<IFunctor>(this, m);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ForkJoin : xaml_functor_container
	{
		public ForkJoin(IRtParent parent)
			: base(parent)
		{
		}

		protected override IFunctor compile()
		{
			var _tmp = ItemsArr;

			if (_tmp.Length == 1)
				return (IFunctor)_tmp[0];

			if (_tmp.Length == 0)
				return f_identity.Instance;

			Type[] rgt = null;
			for (int i = 0; i < _tmp.Length; i++)
			{
				var x = (IFunctor)_tmp[i];
				if (rgt == null)
					rgt = x.Targs;
				else if (!x.Targs.SequenceEqual(rgt))
					throw new Exception();
			}

			Type Tobs;
			if ((Tobs = f_compose.GetActiveObjArgType(rgt[1])) != null)
				return typeof(f_fork<,>).MakeGenericType(rgt[0], Tobs)._ctor<IFunctor>(this, this);

			throw not.impl;
		}
	};
}
